package com.xyy.saas.payment.util;

import com.alibaba.fastjson.JSON;
import com.jddglobal.open.support.http.HttpUtils;
import com.jddglobal.open.support.util.JacksonUtils;
import com.xyy.saas.payment.common.exception.PaymentException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.security.*;
import java.util.*;

/**
 * @author liujf
 */
public class ABChinaUtil {
    private static final String AB_CHINA_PUBLIC_KEY = "04874660348417218721DE3A6DBA9958108A67A829B9852FEC4904F98513AC8F698AB96B91F5846544DA3B9F89FE9FB81E19687A47D859612E2FE9083AEEFE1A66";
    // 不要删除
    private static final String XYY_PUBLIC_KEY = "04F50B49C0F2C9AA307613C765AD58E96BC122542AD0A412FDF8062D0B0BB17A7FB0D4ACA5A474CB164B60BB3C0BE98C307ECC3008C95906E926A3817E10FA3867";
    private static final String XYY_PRIVATE_KEY = "65F996B168F88245783B1C0493BA17F63697FDB7FF64E3DA0F6A508928246DF6";
    private static final String SM3_SIGN_KEY = "04A5C5B99F6476B2AA9CAB901CA81EDDDCDA5A03B39D31DD9B60FE835D88349DB68B8C71161581BD2330DB697017BBBAED09ABDF275B9964EED9D0DFAF4D2BDFA6";


    /*
     * 以下为SM2推荐曲线参数
     */
    public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
    public final static BigInteger SM2_ECC_P = CURVE.getQ();
    public final static BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
    public final static BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();
    public final static BigInteger SM2_ECC_N = CURVE.getOrder();
    public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
    public final static BigInteger SM2_ECC_GX = new BigInteger(
            "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
    public final static BigInteger SM2_ECC_GY = new BigInteger(
            "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
    public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);

    public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT,
            SM2_ECC_N, SM2_ECC_H);
    public static final int CURVE_LEN = getCurveLength(DOMAIN_PARAMS);

    public static final String ALGORITHM_NAME = "SM4";
    public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private static String getValue(String authorization, String key) {
        String[] parts = authorization.split("&");
        for (String part : parts) {
            if (part.startsWith(key)) {
                String[] keyValue = part.split("=");
                return keyValue[1];
            }
        }
        return null;
    }

    public static byte[] decrypt_ECB_Padding(byte[] key, byte[] cipherText)
            throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
            NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {
        Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(cipherText);
    }

    private static Cipher generateECBCipher(String algorithmName, int mode, byte[] key)
            throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
            InvalidKeyException {
        Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        cipher.init(mode, sm4Key);
        return cipher;
    }

    /**
     * @param mode             指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param priKeyParameters 私钥
     * @param sm2Cipher        根据mode不同，需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return 原文。SM2解密返回了数据则一定是原文，因为SM2自带校验，如果密文被篡改或者密钥对不上，都是会直接报异常的。
     * @throws InvalidCipherTextException
     */
    public static byte[] decrypt(SM2Engine.Mode mode, ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
            throws InvalidCipherTextException {
        SM2Engine engine = new SM2Engine(mode);
        engine.init(false, priKeyParameters);
        return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
    }

    /**
     * 验证mac
     *
     * @param srcData
     * @param sm3Hash
     * @param key
     * @return
     */
    public static boolean verify(byte[] srcData, byte[] sm3Hash, byte[] key) {
        byte[] newHash = hmac(key, srcData);
        if (Arrays.equals(newHash, sm3Hash)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 计算SM3 Mac值
     *
     * @param key     key值，可以是任意长度的字节数组
     * @param srcData 原文
     * @return Mac值，对于HMac-SM3来说是32字节
     */
    public static byte[] hmac(byte[] key, byte[] srcData) {
        KeyParameter keyParameter = new KeyParameter(key);
        SM3Digest digest = new SM3Digest();
        HMac mac = new HMac(digest);
        mac.init(keyParameter);
        mac.update(srcData, 0, srcData.length);
        byte[] result = new byte[mac.getMacSize()];
        mac.doFinal(result, 0);
        return result;
    }

    public static byte[] generateKey() throws NoSuchAlgorithmException, NoSuchProviderException {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        kg.init(128, new SecureRandom());
        return kg.generateKey().getEncoded();
    }

    public static byte[] encrypt_ECB_Padding(byte[] key, byte[] data)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
            NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
        Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(data);
    }

    public static byte[] sign(byte[] key, byte[] srcData) {
        return hmac(key, srcData);
    }

    /**
     * @param xHex             十六进制形式的公钥x分量，如果是SM2算法，Hex字符串长度应该是64（即32字节）
     * @param yHex             十六进制形式的公钥y分量，如果是SM2算法，Hex字符串长度应该是64（即32字节）
     * @param curve            EC曲线参数，一般是固定的，如果是SM2算法的可参考{@link #CURVE}
     * @param domainParameters EC Domain参数，一般是固定的，如果是SM2算法的可参考{@link #DOMAIN_PARAMS}
     * @return
     */
    public static ECPublicKeyParameters createECPublicKeyParameters(
            String xHex, String yHex, ECCurve curve, ECDomainParameters domainParameters) {
        return createECPublicKeyParameters(ByteUtils.fromHexString(xHex), ByteUtils.fromHexString(yHex),
                curve, domainParameters);
    }

    /**
     * @param xBytes           十六进制形式的公钥x分量，如果是SM2算法，应该是32字节
     * @param yBytes           十六进制形式的公钥y分量，如果是SM2算法，应该是32字节
     * @param curve            EC曲线参数，一般是固定的，如果是SM2算法的可参考{@link #CURVE}
     * @param domainParameters EC Domain参数，一般是固定的，如果是SM2算法的可参考{@link #DOMAIN_PARAMS}
     * @return
     */
    public static ECPublicKeyParameters createECPublicKeyParameters(
            byte[] xBytes, byte[] yBytes, ECCurve curve, ECDomainParameters domainParameters) {
        final byte uncompressedFlag = 0x04;
        int curveLength = getCurveLength(domainParameters);
        xBytes = fixToCurveLengthBytes(curveLength, xBytes);
        yBytes = fixToCurveLengthBytes(curveLength, yBytes);
        byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length];
        encodedPubKey[0] = uncompressedFlag;
        System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length);
        System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length);
        return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters);
    }

    public static byte[] fixToCurveLengthBytes(int curveLength, byte[] src) {
        if (src.length == curveLength) {
            return src;
        }

        byte[] result = new byte[curveLength];
        if (src.length > curveLength) {
            System.arraycopy(src, src.length - result.length, result, 0, result.length);
        } else {
            System.arraycopy(src, 0, result, result.length - src.length, src.length);
        }
        return result;
    }

    public static int getCurveLength(ECDomainParameters domainParams) {
        return (domainParams.getCurve().getFieldSize() + 7) / 8;
    }

    /**
     * @param mode             指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param pubKeyParameters 公钥
     * @param srcData          原文
     * @return 根据mode不同，输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @throws InvalidCipherTextException
     */
    private static byte[] encrypt(SM2Engine.Mode mode, ECPublicKeyParameters pubKeyParameters, byte[] srcData)
            throws InvalidCipherTextException, NoSuchAlgorithmException {
        SM2Engine engine = new SM2Engine(mode);
        //ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom());
        ParametersWithRandom pwr = null;
        try {
            //linux 请同步修改 /usr/lib64/jvm/java-1.8.0-openjdk-1.8.0/jre/lib/security/java.security
            //securerandom.strongAlgorithms=NativePRNGNonBlocking
            //-Djava.security.egd=file:/dev/./urandom
            pwr = new ParametersWithRandom(pubKeyParameters, SecureRandom.getInstance("NativePRNGNonBlocking"));
        } catch (NoSuchAlgorithmException e) {
            pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom());
        }

        engine.init(true, pwr);
        return engine.processBlock(srcData, 0, srcData.length);
    }

    public static String decrypt(HttpServletRequest request) throws Exception {
        // 验签
        String authorization = request.getHeader("authorization");
        String secretToken = request.getHeader("secretToken");
        String body = HttpUtils.getBodyString(request);
        String encryReqData = (String) (JSON.parseObject(body).get("encryReqData"));

        String nonce_str = getValue(authorization, "nonce_str");
        String timestamp = getValue(authorization, "timestamp");
        String signature = getValue(authorization, "signature");
        String dataToSign = nonce_str + "\n" + timestamp + "\n" + encryReqData;
        if (verify(dataToSign.getBytes(), ByteUtils.fromHexString(signature), SM3_SIGN_KEY.getBytes())) {
            ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(new BigInteger(ByteUtils.fromHexString(XYY_PRIVATE_KEY)), DOMAIN_PARAMS);
            String sm4key = new String(decrypt(SM2Engine.Mode.C1C2C3, priKey, ByteUtils.fromHexString(secretToken)));
            return new String(decrypt_ECB_Padding(ByteUtils.fromHexString(sm4key), ByteUtils.fromHexString(encryReqData)));
        } else {
            throw new IllegalStateException("验签失败！");
        }
    }

    public static void encrypt(Object respObj, HttpServletResponse response) throws Exception {
        String respStr = JSON.toJSONString(respObj);
        String sm4Secret = ByteUtils.toHexString(generateKey());
        String encryptRspStr = ByteUtils.toHexString(encrypt_ECB_Padding(ByteUtils.fromHexString(sm4Secret), respStr.getBytes()));

        byte[] src65 = ByteUtils.fromHexString(AB_CHINA_PUBLIC_KEY);
        byte[] rawXY = new byte[CURVE_LEN * 2];//SM2的话这里应该是64字节
        System.arraycopy(src65, 1, rawXY, 0, rawXY.length);
        String xyHex = ByteUtils.toHexString(rawXY).toUpperCase();
        String xHex = xyHex.substring(0, 64);
        String yHex = xyHex.substring(64);

        ECPublicKeyParameters pubKey = createECPublicKeyParameters(xHex, yHex, CURVE, DOMAIN_PARAMS);
        String sm4EncryptSecret = ByteUtils.toHexString(encrypt(SM2Engine.Mode.C1C2C3, pubKey, sm4Secret.getBytes()));

        // 签名
        long timestamp = new Date().getTime();
        String nonce_str = UUID.randomUUID().toString().trim().replaceAll("-", "");
        String dataToSign = nonce_str + "\n" + timestamp + "\n" + encryptRspStr;

        String signature = ByteUtils.toHexString(sign(SM3_SIGN_KEY.getBytes(), dataToSign.getBytes()));
        String authorization = "nonce_str=" + nonce_str + "&timestamp=" + timestamp + "&signature=" + signature;

        //设置响应
        response.setCharacterEncoding("utf-8");
        Map<String, String> respMap = new HashMap<>(3);
        respMap.put("encryReqData", encryptRspStr);
        respMap.put("authorization", authorization);
        respMap.put("secretToken", sm4EncryptSecret);
        PrintWriter writer = response.getWriter();
        writer.write(JSON.toJSONString(respMap));

        writer.close();
    }
}
